using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;

namespace XmlObjects
{


  /// <summary>
  /// Factory to create TypeHelpers, ensures that one single TypeHelper is 
  ///   created for a given type.
  /// The typeHelperHandler does not support invocations from multiple threads.
  /// The typeHelperHandler behaves as well as a XmlTypeAttribute, allowing clients
  ///   to request the defaut attributes in the current Xml load. For
  ///   example, it is possible to ask typeHelperHandler.InnerTextField to get the
  ///   name of the field holding by default the inner text (if any).
  /// </summary>
  class TypeHelperHandler : XmlTypeAttribute
  {
    public TypeHelperHandler()
    {
      typeAttributeDictionary=new Dictionary<Type,Dictionary<Type,string>>();
      typeHelpers = new Dictionary<Type, TypeHelper>();
    }

    /// <summary>
    /// Returns all the type helpers known by the helper handler
    /// </summary>
    /// <returns></returns>
    public List<TypeHelper> GetAllTypeHelpers()
    {
      return new List<TypeHelper>(typeHelpers.Values);
    }


    /// <summary>
    /// Creates a TypeHelper instance for the given type.
    /// This method can be called multiple times for a given type; only
    ///    one TypeHelper instance is created for a specific type
    /// </summary>
    /// <param name="xmlType"></param>
    /// <returns></returns>
    public TypeHelper CreateInstance(Type xmlType)
    {
      TypeHelper typeHelper;
      if (!typeHelpers.TryGetValue(xmlType, out typeHelper))
      {
        //note that the TypeHelper is first created and then its
        //Build method is invoked. This is required (instead of having
        //one constructor that calls the Build method) to handle recursive
        //types.
        typeHelper = new TypeHelper(xmlType);
        typeHelpers[xmlType] = typeHelper;
        typeHelper.Build(this);
      }
      return typeHelper;
    }




    /// <summary>
    /// Creates an instance of the type defined by 'declaredType', which
    ///   must have the format "className" or "className, assembly".
    /// If the assembly is missing, it is used the assembly of the initialType.
    /// In any case, the resulting type must be assignable to the initialType
    /// </summary>
    /// <param name="declaredType"></param>
    /// <param name="initialType"></param>
    /// <returns></returns>
    public TypeHelper CreateInstance(string declaredType, Type initialType)
    {
      Type t;
      if (declaredType.Contains(","))
        t = Type.GetType(declaredType);
      else
        t = initialType.Assembly.GetType(declaredType);
      if (t == null) 
        throw new XmlObjectsException(XmlObjectsError.InvalidTypeDefinition, 
          declaredType, initialType.Name);
      if (!initialType.IsAssignableFrom(t))
        throw new XmlObjectsException(XmlObjectsError.InvalidTypeDefinitionRelationship,
          declaredType, initialType.Name);
      return CreateInstance(t);
    }





    /// <summary>
    /// Returns a XmlTypeAttribute copy of the current one, but with all
    ///   the attribute fields (ParentField, InnerTextField, ChildsField)
    ///   set to null.
    /// </summary>
    public XmlTypeAttribute XmlTypeAttributeWithoutFields
    {
      get
      {
        if (defaultAtt == null)
        {
          //this  implementation assumes that these values do not change later!
          defaultAtt = new XmlTypeAttribute();
          defaultAtt.AttributeFields = AttributeFields;
          defaultAtt.ExplicitFields = ExplicitFields;
          defaultAtt.ProvidedFieldsSupport = ProvidedFieldsSupport;
        }
        return defaultAtt;
      }
    }


    /// <summary>
    /// Returns the value that should be assigned to the TypeAttribute for the given
    /// instance, which is supposed to have the given associated type
    /// </summary>
    /// <param name="instance"></param>
    /// <param name="associatedType"></param>
    /// <returns></returns>
    public string GetTypeAttributeValue(Type instanceType, Type declaredType)
    {
      string ret=null;
      if (instanceType != declaredType)
      {
        //ok, different type. If they belong to the same namespace, or
        // the real type belongs to the default namespace, the namespace is
        // not returned
        ret=getTypeAttributeDictionaryEntry(instanceType, declaredType);
        if (ret==null)
        {
          ret=instanceType.FullName;
          if (instanceType.Assembly!=declaredType.Assembly)
            ret=ret+", "+instanceType.Assembly.GetName().Name;
          setTypeAttributeDictionaryEntry(instanceType, declaredType, ret);
        }
      }
      return ret;
    }


    private void setTypeAttributeDictionaryEntry(Type instanceType, Type declaredType, 
      string name)
    {
      getTypeAttributeDictionaryEntries(instanceType)[declaredType]=name;
    }

    private string getTypeAttributeDictionaryEntry(Type instanceType, Type declaredType)
    {
      string ret;
      if (getTypeAttributeDictionaryEntries(instanceType).TryGetValue(declaredType, out ret)) 
        return ret;
      return null;
    }

    private Dictionary<Type, string> getTypeAttributeDictionaryEntries(Type instanceType)
    {
      Dictionary<Type, string> ret;
      if (!typeAttributeDictionary.TryGetValue(instanceType, out ret))
      {
        ret = new Dictionary<Type, string>();
        typeAttributeDictionary.Add(instanceType, ret);
      }
      return ret;
    }

    private Dictionary<Type, Dictionary<Type, string>> typeAttributeDictionary;
    private XmlTypeAttribute defaultAtt;
    private Dictionary<Type, TypeHelper> typeHelpers;
  }

}